package com.hd123.rumba.upgrade;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import net.sf.hibernate.HibernateException;

import org.apache.log4j.Logger;

import com.hd123.rumba.cartridge.biz.BCartFunctionView;
import com.hd123.rumba.cartridge.biz.BCartridge;
import com.hd123.rumba.cartridge.biz.IBCartridgeMgr;
import com.hd123.rumba.exception.BusinessException;
import com.hd123.rumba.runtime.NamingMgr;
import com.hd123.rumba.util.Version;

/**
 * 安装/升级基类 | 基类
 * <p>
 * 
 * 关于安装/升级程序及相关升级文件的存放方式的约定参见相关文档。
 * 
 * @author lxg
 * 
 */
public abstract class Upgrade {

  /** 属性：基础版本号 */
  protected Version baseVersion;
  /** 目标版本号 */
  protected Version targetVersion;
  /** 当前数据库版本号 */
  protected Version dbVersion;
  /** 所属构件包Uuid */
  protected String cartUuid;
  /** DBMS类型。取值：VALUE_DBMS_*。 */
  protected String dbms;
  /** 运行的升级程序部分 */
  protected boolean upgradeDdl = false;
  protected boolean upgradeData = false;

  protected Logger logger;

  /**
   * 执行升级处理，错误以异常形式抛出。
   * 
   * @throws BusinessException
   * @throws HibernateException
   */
  public void run(String dbms, boolean upgradeDdl, boolean upgradeData, Version dbVersion)
      throws BusinessException, HibernateException {
    this.dbms = dbms;
    this.dbVersion = dbVersion;
    this.upgradeDdl = upgradeDdl;
    this.upgradeData = upgradeData;
    checkInExecute();
    if (upgradeDdl)
      executeDdl();
    if (upgradeData)
      executeData();
  }

  /**
   * 执行升级前的安全检查。
   * 
   * @throws BusinessException
   */
  private void checkInExecute() throws BusinessException, HibernateException {
    if (upgradeDdl && upgradeData) {
      if (!this.getBaseVersion().truncate(3).equals(dbVersion.truncate(3)))
        throw new BusinessException("数据库当前版本[" + dbVersion + "]不是安装/升级包["
            + this.getClass().getName() + "]的基准版本，不能执行升级！");
    }
  }

  /**
   * 执行数据结构(DDL)升级处理，错误以异常形式抛出。
   * 
   * @throws BusinessException
   */
  protected abstract void executeDdl() throws BusinessException;

  /**
   * 执行初始化数据升级处理，错误以异常形式抛出。
   * 
   * @throws BusinessException
   */
  protected abstract void executeData() throws BusinessException;

  public BCartridge getCartridge() throws BusinessException {
    if (cartridge == null) {
      IBCartridgeMgr cartMgr = (IBCartridgeMgr) NamingMgr.getInstance().lookupMgr(
          IBCartridgeMgr.class);
      cartridge = cartMgr.getCartridge(cartUuid);
    }
    return cartridge;
  }

  /**
   * 指定一个SQL脚本文件进行执行。
   * 
   * @param dbCmd
   *            指定用于指定SQL语句的DBCommand对象。
   * @param fileName
   *            指定脚本文件名及路径。相对以下路径：<i>当前升级对象路径</i>/<i>dbms</i>。
   * @throws BusinessException
   */
  protected void execFile(DBCommand dbCmd, String fileName) throws BusinessException {
    if (dbCmd == null)
      throw new BusinessException("参数dbCmd不可为null。");
    if (fileName == null)
      throw new BusinessException("参数fileName不可为null。");
    if (dbms == null)
      throw new BusinessException(this.getClass().getName() + ".dbms不可为null。");

    InputStream is = getFileInputStream(fileName, dbms);
    if (is == null)
      throw new BusinessException("找不到文件：" + fileName);
    BufferedReader br = null;
    try {
      br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
    } catch (UnsupportedEncodingException e) {
      throw new BusinessException(e);
    }
    ISQLReader reader = null;
    if (CUpgrade.DBMS_ORACLE.equalsIgnoreCase(dbms))
      reader = new OracleSQLReader(br);
    else if (CUpgrade.DBMS_MSSQL.equalsIgnoreCase(dbms))
      reader = new MSSQLSQLReader(br);
    else if (CUpgrade.DBMS_MYSQL.equalsIgnoreCase(dbms))
      reader = new MySQLSQLReader(br);
    else
      throw new BusinessException("无法识别的DBMS: " + dbms);

    ISQLReader.SQLBlock block = reader.readBlock();
    while (block != null) {
      if (ISQLReader.SQLBlock.VALUE_ACTION_PROMPT.equals(block.getAction()))
        doPrompt(fileName, block.getMessage());
      else if (ISQLReader.SQLBlock.VALUE_ACTION_COMMAND.equals(block.getAction()))
        dbCmd.exec(block.getSqls().toString(), block.getIgnore().booleanValue());
      block = reader.readBlock();
    }
  }

  private BCartridge cartridge = null;

  private InputStream getFileInputStream(String fileName, String dbms) {
    String packageName = this.getClass().getPackage().getName();
    String pathName = "/" + packageName.replace(".", "/") + "/" + dbms + "/";
    return this.getClass().getResourceAsStream(pathName + fileName);
  }

  private void doPrompt(String fileName, String message) {
    if (logger != null)
      logger.info("[SQLDOC:" + fileName + "]:" + message);
  }

  public Version getBaseVersion() {
    return baseVersion;
  }

  public String getCartUuid() {
    return cartUuid;
  }

  public String getDbms() {
    return dbms;
  }

  public Version getDbVersion() {
    return dbVersion;
  }

  public Version getTargetVersion() {
    return targetVersion;
  }

  private IBCartridgeMgr cartMgr = null;

  protected IBCartridgeMgr getCartMgr() throws BusinessException {
    if (cartMgr == null)
      cartMgr = (IBCartridgeMgr) NamingMgr.getInstance().lookupMgr(IBCartridgeMgr.class);
    return cartMgr;
  }

  protected <T extends BCartridge> T getCart(Class<T> cartClass) throws BusinessException {
    return (T) getCartMgr().getCartridge(cartClass.getName());
  }

  protected <T extends BCartFunctionView> T getCFV(Class<T> cfvClass) throws BusinessException {
    return (T) getCartMgr().getFunctionView(cfvClass.getName());
  }

}
